﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.Common;
using System.Data;
using System.Globalization;
using System.Text;
using System.Collections;
using System.Net;
using System.IO;
using Newtonsoft.Json;
using System.Collections.Specialized;

/// <summary>
/// 
/// </summary>
public partial class MachineTranslate : System.Web.UI.Page
{
    private Database db;

    /// <summary>
    /// Initializes the base and destination language listboxes.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void Page_Load(object sender, EventArgs e)
    {
        db = new Database();
        db.OpenConnection();
        using(Database.Command cmd = db.CreateCommand()) {
            cmd.CommandText = "SELECT id, displayname FROM " + db.TablePrefix + "applications ORDER BY displayname ASC";
            Database.DataAdapter adapter = new Database.DataAdapter(cmd);
            DataSet ds = new DataSet();
            adapter.Fill(ds);
            if(!Page.IsPostBack) {
                radApplication.DataSource = ds;
                radApplication.DataValueField = "id";
                radApplication.DataTextField = "displayname";
                radApplication.DataBind();
                if(radApplication.Items.Count > 0) {
                    radApplication.SelectedIndex = 0;
                    DataTable googleLanguages = new DataTable();
                    googleLanguages.Columns.Add("code");
                    googleLanguages.Columns.Add("englishName");
                    googleLanguages.DefaultView.Sort = "englishName";
                    foreach(DictionaryEntry language in Constants.GoogleSupportedLanguages) {
                        googleLanguages.Rows.Add(language.Key, language.Value);
                    }
                    lstSourceType.DataSource = googleLanguages;
                    lstSourceType.DataTextField = "englishName";
                    lstSourceType.DataValueField = "code";
                    lstSourceType.DataBind();
                    lstSourceType.SelectedIndex = 0;
                    lstDestinationType.DataSource = googleLanguages;
                    lstDestinationType.DataTextField = "englishName";
                    lstDestinationType.DataValueField = "code";
                    lstDestinationType.DataBind();
                    lstDestinationType.SelectedIndex = 0;
                    ShowLanguages(null, EventArgs.Empty);
                    mltMain.ActiveViewIndex = 1;
                    Page.ClientScript.RegisterClientScriptInclude("blockUI", ResolveUrl("~/js/jquery.block.UI.min.js"));
                    ScriptManager.RegisterClientScriptBlock(this, typeof(Page), "ajaxBlock", @"$(function(){ var prm = Sys.WebForms.PageRequestManager.getInstance();
                        prm.add_endRequest(function() { $('#blockContainer').unblock(); $('#" + btnRefresh.ClientID + @"').remove(); });
                        prm.add_initializeRequest(function(sender, args) { $('#blockContainer').block({ message: ""Please Wait"" }); });
                        $.blockUI.defaults.css = {};
                        $('#notResponding').click(function() { $('#blockContainer').unblock(); return false; }); 
                        $('#" + btnRefresh.ClientID + @"').remove();
                        });
                        ", true);
                }
            }
        }
        db.CloseConnection();
    }


    /// <summary>
    /// Shows the languages and bundles for the application selected.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void ShowLanguages(object sender, EventArgs e) {
        if(radApplication.SelectedValue == null) {
            return;
        }
        db = new Database();
        db.OpenConnection();
        using(Database.Command cmd = db.CreateCommand()) {
            cmd.CommandText = "SELECT languages, defaultLanguage FROM " + db.TablePrefix + "applications WHERE id = @p";
            cmd.AddParameterWithValue("@p", radApplication.SelectedValue);
            List<ListItem> tempSourceLanguages = new List<ListItem>(),
                           tempDestinationLanguages = new List<ListItem>(),
                           tempBundles = new List<ListItem>();
            string rawLanguages = null,
                   defaultLanguage = null,
                   defaultLanguageCode = string.Empty;
            DbDataReader reader = cmd.ExecuteReader(CommandBehavior.SingleRow);
            if(!reader.Read()) {
                mltMain.ActiveViewIndex = 2;
                pnlSuccess.Visible = false;
                pnlError.Visible = true;
                Literal litError = new Literal();
                litError.Text = "<p>Error #410: Application not found</p>";
                plcErrors.Controls.Add(litError);
                return;
            } else {
                rawLanguages = reader["languages"].ToString();
                defaultLanguage = reader["defaultLanguage"].ToString();
                reader.Close();
            }
            foreach(string abbrev in rawLanguages.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) {
                string englishName = null;
                try {
                    englishName = CultureInfo.GetCultureInfo(abbrev).EnglishName;
                } catch {
                    englishName = abbrev;
                }
                ListItem itmSrc = new ListItem(englishName, abbrev);
                if(abbrev == defaultLanguage) {
                    itmSrc.Selected = true;
                    try {
                        defaultLanguageCode = Utils.ISOCode2GoogleCode(CultureInfo.GetCultureInfo(abbrev).TwoLetterISOLanguageName);
                    } catch {
                        System.Diagnostics.Debug.WriteLine("Invalid language code for " + abbrev);
                    }
                }
                tempSourceLanguages.Add(itmSrc);
                tempDestinationLanguages.Add(new ListItem(englishName, abbrev));
            }
            ListComparer cmp = new ListComparer();
            tempSourceLanguages.Sort(cmp);
            tempDestinationLanguages.Sort(cmp);
            lstSourceLanguage.Items.Clear();
            lstDestinationLanguage.Items.Clear();
            lstSourceLanguage.Items.AddRange(tempSourceLanguages.ToArray());
            lstDestinationLanguage.Items.AddRange(tempDestinationLanguages.ToArray());

            lstBundles.Items.Clear();
            cmd.CommandText = "SELECT id, name FROM " + db.TablePrefix + "bundles WHERE application = @p";
            reader = cmd.ExecuteReader();
            while(reader.Read()) {
                ListItem itm = new ListItem(reader["name"].ToString(), reader["id"].ToString());
                itm.Selected = true;
                tempBundles.Add(itm);
            }
            tempBundles.Sort(cmp);
            lstBundles.Items.AddRange(tempBundles.ToArray());
            if(lstBundles.Items.Count == 0) {
                ListItem itm = new ListItem("No bundles found", "");
                itm.Selected = true;
                lstBundles.Items.Add(itm);
                lstBundles.Enabled = false;
            } else {
                lstBundles.Enabled = true;
            }
            reader.Close();

            if(lstSourceLanguage.Items.Count == 0) {
                lstSourceLanguage.Items.Add(new ListItem("No languages found", ""));
                lstSourceLanguage.Enabled = false;
                lstDestinationLanguage.Items.Add(new ListItem("No languages found", ""));
                lstDestinationLanguage.Enabled = false;
            } else {
                lstSourceLanguage.Enabled = true;
                lstDestinationLanguage.Enabled = true;
                lstDestinationLanguage.SelectedIndex = 0;
                if(!string.IsNullOrEmpty(defaultLanguageCode)) {
                    lstSourceType.SelectedValue = defaultLanguageCode;
                }
            }
            btnStartTranslation.Enabled = lstBundles.Enabled && lstSourceLanguage.Enabled;
        }
        db.CloseConnection();
    }

    /// <summary>
    /// Checks a source and destinanation language have been selected and they are different
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">The <see cref="System.Web.UI.WebControls.ServerValidateEventArgs"/> instance containing the event data.</param>
    protected void vldLanguages_ServerValidate(object sender, ServerValidateEventArgs e) {
        if(lstSourceLanguage.SelectedItem == null) {
            vldLanguages.ErrorMessage = "The source language must be specified";
            e.IsValid = false;
        } else if(lstDestinationLanguage.SelectedItem == null) {
            vldLanguages.ErrorMessage = "The destination language must be specified";
            e.IsValid = false;
        } else if(lstSourceLanguage.SelectedItem.Value == lstDestinationLanguage.SelectedItem.Value || lstSourceType.SelectedItem.Value == lstDestinationType.SelectedItem.Value) {
            vldLanguages.ErrorMessage = "The source and destination languages must be different";
            e.IsValid = false;
        }
    }

    protected void btnStartTranslation_Click(object sender, EventArgs e) {
        if(db == null || !db.Connected) {
            db = new Database();
            db.OpenConnection();
        }
        mltMain.ActiveViewIndex = 2;
        try {
            using(Database.Command cmd = db.CreateCommand()) {
                List<string> bundles = new List<string>();
                foreach(ListItem itm in lstBundles.Items) {
                    if(itm.Selected) {
                        bundles.Add(itm.Value);
                    }
                }
                StringBuilder sqlBuffer = new StringBuilder("SELECT r.resourceid, r.translation FROM ")
                            .Append(db.TablePrefix).Append(@"elements AS e
                                            INNER JOIN ").Append(db.TablePrefix).Append(@"resources AS r ON r.resourceid = e.resourceid 
                                            WHERE applicationid = @pID AND language = @lang").Append(db.GenerateInClause("bundle", bundles.ToArray(), true))
                                       .Append("GROUP BY r.resourceid, r.translation");
                cmd.CommandText = sqlBuffer.ToString();
                sqlBuffer.Remove(0, sqlBuffer.Length);
                sqlBuffer.Append("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&").Append(string.Format("langpair={0}|{1}", lstSourceType.SelectedValue, lstDestinationType.SelectedValue));
                cmd.AddParameterWithValue("@pID", radApplication.SelectedValue);
                cmd.AddParameterWithValue("@lang", lstSourceLanguage.SelectedValue);
                List<int> resourceMap = new List<int>();
                DbDataReader reader = cmd.ExecuteReader();
                while(reader.Read()) { 
                    try {
                        string resourceRequest = HttpUtility.UrlEncode(reader[1].ToString());
                        if(sqlBuffer.Length + resourceRequest.Length < 4900) { // ToS limits any request to 5000 chars
                            resourceMap.Add(int.Parse(reader[0].ToString()));
                            sqlBuffer.Append("&q=").Append(HttpUtility.UrlEncode(resourceRequest));
                        } else {
                            lstErrors.Items.Add("Google's <a href=\"http://code.google.com/intl/de/apis/ajaxlanguage/terms.html\">terms of service</a> prohibit requests larger than 5000 characters. Try translating each bundle separately.");
                            lstErrors.Items.Add("Skipping remaining elements...");
                            break;
                        }
                    } catch(Exception ex) {
                        lstErrors.Items.Add("Error reading element: " + ex.Message);
                    }
                }
                reader.Close();
                if(resourceMap.Count == 0) {
                    throw new Exception("No elements found to translate");
                }

                HttpWebRequest req = null;
                req = (HttpWebRequest)WebRequest.Create(sqlBuffer.ToString());
                req.Method = "GET";
                req.ProtocolVersion = HttpVersion.Version11;
                req.AllowAutoRedirect = false;
                req.Accept = "*/*";
                req.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;.NET CLR 2.0.50727)";
                req.Headers.Add("Accept-Language", "en-us");
                req.KeepAlive = true;
                StreamReader resReader = null;
                JsonSerializer json = new JsonSerializer();
                GoogleBatchResponse jso;
                try {
                    HttpWebResponse wr = (HttpWebResponse)req.GetResponse();
                    resReader = new StreamReader(wr.GetResponseStream());
                } catch(Exception ex) {
                    throw new Exception("Error translating elements: " + ex.Message);
                }
                try {
                    jso = (GoogleBatchResponse)json.Deserialize(resReader, typeof(GoogleBatchResponse));
                } catch(Exception ex) {
                    throw new Exception("Error parsing response from Google: " + ex.Message);
                }
                cmd.Parameters.Clear();
                cmd.AddParameterWithValue("@lang", lstDestinationLanguage.SelectedItem.Value);
                cmd.AddParameterWithValue("@tID", "");
                cmd.AddParameterWithValue("@trans", "");
                int numCopied = 0;
                if(radIfExists.SelectedValue == "leave") {
                    cmd.CommandText = "INSERT INTO " + db.TablePrefix + "resources (resourceid, language, translation) VALUES (@tID, @lang, @trans)";
                    for(int i=0;i<resourceMap.Count;i++) {
                        cmd.Parameters["@tID"].Value = resourceMap[i];
                        GoogleResponse translation = jso.responseData[i];
                        if(translation.responseStatus != 200) {
                            lstErrors.Items.Add(string.Format("Could not translate resource #{0}: {1}", resourceMap[i], translation.responseDetails));
                        }
                        try {
                            cmd.Parameters["@trans"].Value = translation.responseData.translatedText;
                            cmd.ExecuteNonQuery();
                            lstSuccess.Items.Add(string.Format("Translated resource #{0}...", resourceMap[i]));
                            numCopied++;
                        } catch { // errors thrown when a translation exists
                            lstSuccess.Items.Add(string.Format("Translation for resource #{0} exists, skipping...", resourceMap[i]));
                        }
                    }
                } else if(radIfExists.SelectedValue == "overwrite") {
                    switch(db.InstanceType) {
                        case Database.DatabaseType.MySQL:
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "resources (resourceid, language, translation) VALUES (@tID, @lang, @trans) ON DUPLICATE KEY UPDATE translation = @trans";
                            break;
                        case Database.DatabaseType.SQLServer:
                            cmd.CommandText = "UPDATE " + db.TablePrefix + "resources SET translation = @trans WHERE resourceid = @tID AND language = @lang; IF @@ROWCOUNT = 0 INSERT INTO " + db.TablePrefix + "resources (resourceid, language, translation) VALUES (@tID, @lang, @trans)";
                            break;
                    }
                    for(int i = 0; i < resourceMap.Count; i++) {
                        cmd.Parameters["@tID"].Value = resourceMap[i];
                        GoogleResponse translation = jso.responseData[i];
                        if(translation.responseStatus != 200) {
                            lstErrors.Items.Add(string.Format("Could not translate resource #{0}: {1}", resourceMap[i], translation.responseDetails));
                        }
                        try {
                            cmd.Parameters["@trans"].Value = HttpUtility.UrlDecode(translation.responseData.translatedText);
                            cmd.ExecuteNonQuery();
                            lstSuccess.Items.Add(string.Format("Overwriting translation for resource #{0}...", + resourceMap[i]));
                            numCopied++;
                        } catch { // errors thrown when a translation exists
                            lstSuccess.Items.Add(string.Format("Error adding translation for resource #{0}: ", resourceMap[i]));
                        }
                    }
                }
                if(lstSuccess.Items.Count > 0) {
                    pnlSuccess.Visible = true;
                    StringBuilder buffer = new StringBuilder("<p>");
                    buffer.AppendFormat("Translated {0} resources from {1} ({2}) to {3} ({4})", numCopied, lstSourceLanguage.SelectedItem.Text, lstSourceType.SelectedItem.Text, lstDestinationLanguage.SelectedItem.Text, lstDestinationType.SelectedItem.Text).Append("</p>");
                    plcSuccess.Controls.Add(new LiteralControl(buffer.ToString()));
                    plcSuccess.Controls.Add(lstSuccess);
                } else {
                    pnlSuccess.Visible = false;
                }
                if(lstErrors.Items.Count > 0) {
                    pnlError.Visible = true;
                    plcErrors.Controls.Add(lstErrors);
                } else {
                    pnlError.Visible = false;
                }
            }
        } catch(Exception ex) {
            pnlError.Visible = true;
            plcErrors.Controls.Add(new LiteralControl("<p>"+ex.Message+"</p>"));
            pnlSuccess.Visible = false;
        }
    }

    /// <summary>
    /// Returned when multiple items are translated
    /// Enables parsing of a JSON response from the Google Translate API
    /// </summary>
    private struct GoogleBatchResponse {
        public GoogleResponse[] responseData { get; set; }
        public string responseDetails { get; set; }
        public int responseStatus { get; set; }
    }

    /// <summary>
    /// Returned when a single string is translated
    /// Enables parsing of a JSON response from the Google Translate API
    /// </summary>
    private struct GoogleResponse {
        public Translation responseData { get; set; }
        public string responseDetails { get; set; }
        public int responseStatus { get; set; }

        public class Translation {
            public string translatedText { get; set; }
        }
    }
}
